from typing import Tuple
import uuid as lib_uuid
from string import Template

from django.db import models
from django.utils.translation import gettext, gettext_lazy as _
from django.core.exceptions import ValidationError
import shortuuid

from apps.app_users.models import UserProfile, Email
from . import notice_templates


class ShortUuidModel(models.Model):
    id = models.CharField(blank=True, editable=False, max_length=32, primary_key=True, verbose_name='ID')

    class Meta:
        abstract = True

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        self.enforce_id()
        super().save(force_insert=force_insert, force_update=force_update,
                     using=using, update_fields=update_fields)

    def enforce_id(self):
        """确保id有效"""
        if not self.id:
            self.id = self.generate_id()

        return self.id

    @staticmethod
    def generate_id() -> str:
        return shortuuid.ShortUUID(alphabet='0123456789abcdefghijkmnopqrstuvwxyz').encode(lib_uuid.uuid1())


class MeetingRoomType(models.IntegerChoices):
    """
    会议室规格类型
    """
    F_50 = 50, _('50方')
    F_100 = 100, _('100方')
    F_300 = 300, _('300方')
    F_500 = 500, _('500方')
    F_1000 = 1000, _('1000方')


class TxMeetingConfig(models.Model):
    """tx_app全局配置表"""

    class ConfigName(models.TextChoices):
        TX_APP_ID = 'tx_app_id', _('腾讯会议企业app_id')
        TX_SDK_ID = 'tx_sdk_id', _('腾讯会议自建应用sdk_id')
        TX_SECRET_ID = 'tx_secret_id', _('腾讯会议自建应用密钥id')
        TX_SECRET_KEY = 'tx_secret_key', _('腾讯会议自建应用密钥key')
        TX_OPERATOR_ID = 'tx_operator_id', _('腾讯会议企业管理员id')
        RESERVE_ROOM_ADVANCE_MINUTTES = 'reserve_room_advance_minutes', _('提前（预定开始时间）多少分钟预留会议室资源')
        MEETING_AFTER_RESERVE_MINUTES = 'meeting_after_reserve_minutes', _('会议预定结束时间后，会议室资源多预留多少分钟')
        DEFAULT_ADVANCE_NOATICE_MINUTES = 'default_advance_notice_minutes', _('默认提前多少分钟发送会议提醒通知')
        RESERVE_TERMS_CONDITIONS = 'reserve_terms_conditions', _('会议预定须知')
        EMAIL_USE_TLS = 'email_use_tls', _('邮件，是否使用TLS安全加密')
        EMAIL_USE_SSL = 'email_use_ssl', _('邮件，是否使用SSL安全加密')
        EMAIL_PORT = 'email_port', _('邮件，端口')
        EMAIL_HOST = 'email_host', _('邮件，域名Host')
        EMAIL_HOST_USER = 'email_host_user', _('邮件，邮件地址')
        EMAIL_HOST_PASSWORD = 'email_host_password', _('邮件，密码')
        PRE_MEET_NOTICE_TEMPLATE = 'pre_meet_notice_template', _('会前通知模板')
        NO_SHOW_NOTICE_ENABLED = 'noshow_notice_enable', _('开启违约邮件通知')
        NO_SHOW_PENALTY_ENABLED = 'noshow_penalty_enable', _('开启违约惩罚限制')

    value_defaults = {
        ConfigName.TX_APP_ID.value: '',
        ConfigName.TX_SDK_ID.value: '',
        ConfigName.TX_SECRET_ID.value: '',
        ConfigName.TX_SECRET_KEY.value: '',
        ConfigName.TX_OPERATOR_ID.value: {
            'value': '', 'remark': '腾讯会议企业下的有管理权限的用户ID'
        },
        ConfigName.RESERVE_ROOM_ADVANCE_MINUTTES.value: '30',
        ConfigName.MEETING_AFTER_RESERVE_MINUTES.value: {
            'value': '30', 'remark': '会议预定结束时间后，会议室资源多预留多少分钟，比如会议可能拖堂，虚拟会议室资源释放等也需要一点时间'
        },
        ConfigName.DEFAULT_ADVANCE_NOATICE_MINUTES.value: '30',
        ConfigName.RESERVE_TERMS_CONDITIONS.value: '',
        ConfigName.EMAIL_USE_TLS.value: {
            'value': 'false', 'remark': '"false"为不使用TLS安全传输协议，"true"为使用TLS；注意：SSL和TLS只能选其一'
        },
        ConfigName.EMAIL_USE_SSL.value: {
            'value': 'true', 'remark': '"false"为不使用SSL安全传输协议，"true"为使用SSL；注意：SSL和TLS只能选其一'
        },
        ConfigName.EMAIL_PORT.value: {'value': '465', 'remark': '邮件端口'},
        ConfigName.EMAIL_HOST.value: {'value': 'mail.cstnet.cn', 'remark': '邮件域名Host'},
        ConfigName.EMAIL_HOST_USER.value: {'value': '', 'remark': '使用此邮件地址向用户发送邮件'},
        ConfigName.EMAIL_HOST_PASSWORD.value: {'value': '', 'remark': '邮件地址的密码'},
        ConfigName.PRE_MEET_NOTICE_TEMPLATE.value: {
            'value': notice_templates.PRE_MEET_NOTICE_TEMPLATE,
            'remark': '模板中使用格式"${key}"方式替换填充会议信息，key可选值：meeting_subject（会议主题）、meeting_time（会议时间）、'
                      'meeting_code（腾讯会议号）、meeting_join_url（入会链接）、meeting_userid（预订人账号）。'
                      '如果内容中你想输出1个特殊字符“$”，需要多输入1个“$”对其进行转义，即“$$”。'
        },
        ConfigName.NO_SHOW_NOTICE_ENABLED.value: {'value': 'true', 'remark': '"true"开启违约邮件通知，"false"为关闭邮件通知'},
        ConfigName.NO_SHOW_PENALTY_ENABLED.value: {'value': 'true', 'remark': '"true"开启违约惩罚，"false"为关闭'},
    }

    id = models.BigAutoField(primary_key=True, verbose_name=_('ID'))
    name = models.CharField(verbose_name=_('配置名称'), max_length=32, choices=ConfigName.choices)
    value = models.TextField(verbose_name=_('配置内容'), blank=True, default='')
    remark = models.CharField(verbose_name=_('备注'), blank=True, max_length=254)
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        verbose_name = _('会议预定服务基本配置')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_configs'
        ordering = ['-creation_time']
        constraints = [
            models.UniqueConstraint(fields=('name',), name='unique_tx_meet_config_name')
        ]

    def __str__(self):
        return self.name

    def clean(self):
        # 时间设置需要是正整数，15分钟
        if self.name in [
            self.ConfigName.RESERVE_ROOM_ADVANCE_MINUTTES.value,
            self.ConfigName.MEETING_AFTER_RESERVE_MINUTES.value,
            self.ConfigName.DEFAULT_ADVANCE_NOATICE_MINUTES.value
        ]:
            try:
                val = int(self.value)
            except ValueError:
                raise ValidationError({'value': gettext('必须是一个正整数，并且不得小于15')})

            if not (15 <= val <= 60 * 3):
                raise ValidationError({'value': gettext('可选范围为15-180分钟')})

        elif self.name == self.ConfigName.DEFAULT_ADVANCE_NOATICE_MINUTES.value:
            try:
                val = int(self.value)
            except ValueError:
                raise ValidationError({'value': gettext('必须是一个正整数，并且不得小于15')})

            if val < 15:
                raise ValidationError({'value': gettext('不能小于15分钟')})

        elif self.name == self.ConfigName.EMAIL_PORT.value:
            try:
                val = int(self.value)
                if val < 0:
                    raise ValueError
            except ValueError:
                raise ValidationError({'value': gettext('邮件服务端口必须是一个正整数，常用端口为25(非加密)、465(SSL)、587(TLS)')})

        elif self.name in [
            self.ConfigName.EMAIL_USE_TLS.value, self.ConfigName.EMAIL_USE_SSL.value,
            self.ConfigName.NO_SHOW_NOTICE_ENABLED.value, self.ConfigName.NO_SHOW_PENALTY_ENABLED.value
        ]:
            if self.value not in ['false', 'true']:
                raise ValidationError({'value': gettext('可选值为“false”或者“true”')})

        elif self.name == self.ConfigName.PRE_MEET_NOTICE_TEMPLATE.value:
            try:
                Template(self.value).substitute(
                    meeting_userid='username',
                    meeting_subject='主题',
                    meeting_time='会议时间',
                    meeting_code='会议号',
                    meeting_join_url='https://test.com/test',
                )
            except Exception as e:
                raise ValidationError({'value': gettext('会前通知模板格式有误。') + str(e)})


class MeetingRoom(ShortUuidModel):
    """会议资源配置表"""
    # 定义虚拟会议室资源配置主记录id，只能有一条记录，防止多条记录
    MAIN_ID = 'main'

    type_50_num = models.IntegerField(verbose_name=_('50方数'), default=0)
    type_100_num = models.IntegerField(verbose_name=_('100方数'), default=0)
    type_300_num = models.IntegerField(verbose_name=_('300方数'), default=0)
    type_500_num = models.IntegerField(verbose_name=_('500方数'), default=0)
    type_1000_num = models.IntegerField(verbose_name=_('1000方数'), default=0)
    type_50_visible = models.BooleanField(verbose_name=_('50方可见'), default=False)
    type_100_visible = models.BooleanField(verbose_name=_('100方可见'), default=False)
    type_300_visible = models.BooleanField(verbose_name=_('300方可见'), default=False)
    type_500_visible = models.BooleanField(verbose_name=_('500方可见'), default=False)
    type_1000_visible = models.BooleanField(verbose_name=_('1000方可见'), default=False)
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('修改时间'), auto_now=True)

    class Meta:
        verbose_name = _('会议室资源')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_rooms'
        ordering = ['-creation_time']

    @classmethod
    def get_main_instance(cls, select_for_update: bool = False) -> 'MeetingRoom':
        if select_for_update:
            obj = cls.objects.filter(id=cls.MAIN_ID).select_for_update().first()
        else:
            obj = cls.objects.filter(id=cls.MAIN_ID).first()

        if obj is not None:
            return obj

        obj = cls(
            id=cls.MAIN_ID,
            type_50_num=0,
            type_100_num=0,
            type_300_num=0,
            type_500_num=0,
            type_1000_num=0
        )
        obj.save(force_insert=True)

        if select_for_update:
            obj = cls.objects.filter(id=cls.MAIN_ID).select_for_update().first()

        return obj

    def get_room_num(self, room_type: int) -> int:
        num, visible = self.get_room_config(room_type)
        return num

    def get_room_config(self, room_type: int) -> Tuple[int, bool]:
        """
        获取指定类型的房间资源配置，数量和是否可见
        """
        if room_type not in MeetingRoomType.values:
            raise ValueError(f'Invalid room type: {room_type}')

        return {
            MeetingRoomType.F_50.value: (self.type_50_num, self.type_50_visible),
            MeetingRoomType.F_100.value: (self.type_100_num, self.type_100_visible),
            MeetingRoomType.F_300.value: (self.type_300_num, self.type_300_visible),
            MeetingRoomType.F_500.value: (self.type_500_num, self.type_500_visible),
            MeetingRoomType.F_1000.value: (self.type_1000_num, self.type_1000_visible)
        }[room_type]


class MeetingUser(ShortUuidModel):
    """用户权限表"""

    class Role(models.TextChoices):
        ADMIN = 'admin', _('管理员')
        REGULAR = 'regular', _('普通用户')

    class UserStatus(models.TextChoices):
        ACTIVE = 'active', _('有效')
        INACTIVE = 'inactive', _('无效')

    user = models.OneToOneField(to=UserProfile, on_delete=models.CASCADE, verbose_name=_('用户'))
    role = models.CharField(
        verbose_name=_('用户角色'), max_length=32, choices=Role.choices, default=Role.ADMIN.value)
    status = models.CharField(
        verbose_name=_('用户状态'), max_length=32, choices=UserStatus.choices, default=UserStatus.ACTIVE.value)
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('修改时间'), auto_now=True)

    class Meta:
        verbose_name = _('会议预定的用户')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_users'
        ordering = ['-creation_time']

    def is_admin(self) -> bool:
        return self.role == self.Role.ADMIN.value

    def is_active(self) -> bool:
        return self.status == self.UserStatus.ACTIVE.value


class MeetingReservation(ShortUuidModel):
    """ 腾讯会议预定记录"""
    class Status(models.TextChoices):
        RESERVED = 'reserved', _('已预定')
        CANCELLED = 'cancelled', _('已取消')
        NO_SHOW = 'noshow', _('违约')

    class RoomReserveStatus(models.TextChoices):
        WAIT = 'wait', _('待预留')
        UNRESERVED = 'unreserved', _('预留失败')
        RESERVED = 'reserved', _('已预留')
        UNRELEASED = 'unreleased', _('释放失败')
        RELEASED = 'released', _('已释放')

    # 用户的预定信息
    subject = models.CharField(verbose_name=_('会议主题'), max_length=512)
    start_time = models.BigIntegerField(verbose_name=_('预定开始时间'), help_text=_('用户预定的开始时间'))
    end_time = models.BigIntegerField(verbose_name=_('预定结束时间'), help_text=_('用户预定的结束时间'))
    room_reserve_start = models.BigIntegerField(verbose_name=_('会议室预留开始时间'))
    room_reserve_end = models.BigIntegerField(
        verbose_name=_('会议室预留结束时间'),
        help_text=_('会议室资源预留的最终时间，会议室资源释放必须在此时间之前和用户预定结束时间之后'))
    people_num = models.IntegerField(verbose_name=_('会议人数'))
    room_type = models.IntegerField(
        verbose_name=_('会议室类型'), choices=MeetingRoomType.choices, default=MeetingRoomType.F_50.value)
    status = models.CharField(
        verbose_name=_('预定状态'), max_length=16, choices=Status.choices, default=Status.RESERVED.value)
    user = models.ForeignKey(
        to=UserProfile, verbose_name=_('预定用户'), on_delete=models.SET_NULL, null=True, blank=True,
        related_name='+'
    )
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('修改时间'), auto_now=True)

    # 腾讯会议的信息
    tx_start_time = models.BigIntegerField(verbose_name=_('腾讯会议开始时'))
    tx_end_time = models.BigIntegerField(verbose_name=_('腾讯会议结束时'))
    tx_meeting_id = models.CharField(verbose_name=_('腾讯会议id'), max_length=32)
    tx_meeting_userid = models.CharField(verbose_name=_('腾讯会议创建者'), max_length=128)
    tx_user_nick_name = models.CharField(verbose_name=_('会议创建者昵称'), max_length=128, blank=True, default='')
    tx_meeting_code = models.CharField(verbose_name=_('腾讯会议号'), max_length=16)
    tx_join_url = models.CharField(verbose_name=_('腾讯会议加入链接'), max_length=254)

    # 虚拟会议室资源预留
    room_reserve_status = models.CharField(
        verbose_name=_('会议室预留状态'), max_length=16,
        choices=RoomReserveStatus.choices, default=RoomReserveStatus.WAIT.value,
        help_text=_('去腾讯会议请求为用户预留虚拟会议室资源的状态')
    )
    room_reserve_desc = models.CharField(
        verbose_name=_('会议室预留状态描述'), max_length=254, blank=True, default='',
        help_text=_('请求腾讯会议预留和释放虚拟会议室资源的结果描述，可以记录失败信息'))

    # 会前通知
    advance_notice_seconds = models.BigIntegerField(
        verbose_name=_('发送会议提醒通知提前秒数'), help_text=_('用户设置的会前通知时间，会前提前多少秒给用户发送会议提醒通知'))
    need_notice_time = models.BigIntegerField(verbose_name=_('需要发送通知的时间'), help_text=_('当到达此时间时'))
    email_notice = models.ForeignKey(
        verbose_name=_('邮件通知记录'), to=Email, related_name='+',
        null=True, on_delete=models.SET_NULL, blank=True)

    # need_sms_notice = models.BooleanField(verbose_name=_('需要短信通知'), default=False, help_text=_('是否需要发送短信通知，用户可选'))

    class Meta:
        verbose_name = _('会议预定')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_reservation'
        ordering = ['-creation_time']
        indexes = [
            models.Index(fields=['room_reserve_start', 'room_reserve_end'], name='idx_meet_reserv_room_start_end'),
            models.Index(fields=['creation_time'], name='idx_meet_reserve_creation_time'),
            models.Index(fields=['start_time', 'end_time'], name='idx_meet_reserv_start_end'),
        ]

    def __str__(self):
        return f'{self.subject}[{self.start_time}-{self.end_time}]'

    @property
    def advance_notice_minutes(self) -> int:
        """
        会前通知提前多少分钟
        """
        return self.advance_notice_seconds // 60

    @advance_notice_minutes.setter
    def advance_notice_minutes(self, minutes: int):
        """
        设置会前通知提前多少分钟，不会更新到数据库

            * 会一起更新需要发送通知的时间
        """
        self.advance_notice_seconds = minutes * 60
        self.need_notice_time = self.start_time - self.advance_notice_seconds


class MeetingReservationLog(ShortUuidModel):
    """
    会议预定 相关操作日志
    """
    class LogType(models.TextChoices):
        OPERATE = 'operate', _('会议预定操作')
        NOTICE = 'notice', _('会前通知')
        ROOM = 'room', _('会议室预留和释放')
        OTHOR = 'other', _('其他')

    class OperateStatus(models.TextChoices):
        OK = 'ok', _('成功')
        FAIL = 'fail', _('失败')
        NONE = 'none', _('无')

    log_type = models.CharField(verbose_name=_('日志类型'), max_length=16, choices=LogType.choices)
    content = models.TextField(verbose_name=_('内容'))
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    reservation_id = models.CharField(verbose_name=_('会议预定id'), max_length=32)
    operator = models.CharField(verbose_name=_('操作人'), max_length=128, blank=True, default='')
    operate_status = models.CharField(
        verbose_name=_('操作结果'), max_length=16, choices=OperateStatus.choices, default=OperateStatus.OK.value)

    class Meta:
        verbose_name = _('会议预定操作日志')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_reservation_log'
        ordering = ['-creation_time']
        indexes = [
            models.Index(fields=['creation_time'], name='idx_meet_reserve_log_creation'),
            models.Index(fields=['reservation_id'], name='idx_meet_log_reserve_id'),
        ]

    @classmethod
    def add_log(
            cls, log_type: str, operate_status: str,
            content: str, reservation_id: str, operator: str = ''
    ):
        obj = cls(
            log_type=log_type,
            content=content,
            reservation_id=reservation_id,
            operator=operator,
            operate_status=operate_status
        )
        obj.save(force_insert=True)
        return obj


class ReservationNoShow(ShortUuidModel):
    class ValidStatus(models.TextChoices):
        VALID = 'valid', _('有效')
        INVALID = 'invalid', _('无效')
        EXEMPT = 'exempt', _('豁免')

    reservation = models.OneToOneField(
        verbose_name=_('会议预定'), to=MeetingReservation, related_name='+', on_delete=models.SET_NULL, null=True,
        blank=True)
    username = models.CharField(verbose_name=_('违约人账号'), max_length=128, default='')
    user_nickname = models.CharField(verbose_name=_('违约人昵称'), max_length=64, default='')
    creation_time = models.DateTimeField(verbose_name=_('违约时间'))
    valid_status = models.CharField(
        verbose_name=_('有效状态'), max_length=16, choices=ValidStatus.choices, default=ValidStatus.VALID.value,
        help_text=_('标记本条违约记录是否有效，有效违约记录会用于惩罚限制用户的预定会议。')
    )
    remarks = models.CharField(verbose_name=_('备注'), max_length=254, blank=True, default='')
    email_notice = models.ForeignKey(
        verbose_name=_('邮件通知记录'), to=Email, related_name='+',
        null=True, on_delete=models.SET_NULL, blank=True, default=None)

    class Meta:
        verbose_name = _('会议预定违约记录')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_noshow'
        ordering = ['-creation_time']
        indexes = [
            models.Index(fields=['creation_time'], name='idx_meet_noshow_creation'),
            models.Index(fields=['username'], name='idx_meet_noshow_username'),
        ]


class NoShowWhitelist(ShortUuidModel):
    username = models.CharField(verbose_name=_('用户名'), max_length=128, default='')
    user_nickname = models.CharField(verbose_name=_('用户昵称'), max_length=64, blank=True, default='')
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('修改时间'), auto_now=True)
    remarks = models.CharField(verbose_name=_('备注'), max_length=254, blank=True, default='')
    creator = models.CharField(verbose_name=_('创建人'), max_length=128, default='')

    class Meta:
        verbose_name = _('违约白名单')
        verbose_name_plural = verbose_name
        db_table = 'tx_meeting_noshow_whitelist'
        ordering = ['-creation_time']
        constraints = [
            models.UniqueConstraint(fields=['username'], name='unique_meet_ns_wl_username'),
        ]

    def __str__(self):
        return self.username
